Fatal error: Index out of range
不關菜鳥或是資深,在開發多過程中總是三不五時的遇到上面這個問題。
但是!在某些情況下,我們或許不在乎是否超出邊界,舉個例子:
假設我們畫面上固定顯示 10 筆資料,如果實際資料不足 10 筆,剩餘的部分就使用另外一種空值狀態,那麼我們可以針對 out of range (nil) 的狀況,來將畫面設置成空值的狀態。
承上,我們可以加上一些邊界的判斷:
extension Array {
subscript(safe index: Int) -> Element? {
if index < 0 || index > count - 1{
return nil
} else {
return self[index]
}
}
}
let arr = [0, 1]
arr[safe: 2] // nill
或者使用 contains
:
extension Array {
subscript(safe index: Int) -> Element? {
if !indices.contains(index) {
return nil
} else {
return self[index]
}
}
}
let arr = [0, 1]
arr[safe: 2] // nill
這在 stackoverflow 上有很多類似的做法。
看起來完美的解決的我們的問題,但是仔細看,會發現一點小小的問題是,safe:
表示的是什麼?它看起來像是針對 subscript parameter
的描述,但是沒有指明參數的作用是 Array index
,如果今天使用這個 subscript 的人沒有看文件,沒有看到原始碼,是容易造成困惑的。
一個好的命名應該是不用看文件,也可以理解 API 的使用。
話雖如此,但我還是使用了上面的寫法很長一段時間,因為我上面的 extension 是我寫的,所以並不會也困惑產生。直到某一天,我看到一篇文章(已經找不到出處了),針對 safe 這個命名空間,發表了一個我認為更好的寫法:
arr.safe[0]
這樣的寫法使用的 .
作為命名空間的間隔, .safe
看起來更像是,接下來的動作對於 array 而言是安全的操作,接下來用 Swift 對 Array index 的使用習慣: subscript with index
來做安全取值。
那麼我們要怎麼寫,才能達到上面的使用方法呢:
public struct SafeCollectionable<Base> where Base: Collection {
let base: Base
init(_ base: Base) {
self.base = base
}
public subscript(_ index: Base.Index) -> Base.Element? {
if !base.indices.contains(index) {
return nil
}
return base[index]
}
}
public extension Collection {
var safe: SafeCollectionable<Self> {
return SafeCollectionable(self)
}
}
完成囉!宣告一個 SafeCollectionable
,然後利用 extension
為 Collection
增加一個 safe: SafeCollectionable
的 computed property
就可以達到我們想要的效果了!
歡迎到我的網站逛逛。
實用的文章!
分享 test case
func testArraySafe() {
let list = [1,2]
var index = 3
let output = list.safe[index]
XCTAssertNil(output)
index = 1
XCTAssertEqual(list[index], list.safe[index])
}
感謝 text case ~